﻿using System;
using System.IO;
using System.Configuration;
using System.Security.Cryptography;
using System.Data.SqlClient;
using System.Data;
using template_dll;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Principal;

namespace WcfServiceExchangeFiles
{
    public class ExchangeFiles : IExchangeFiles
    {
        private string PATH_IN;
        private string PATH_OUT;
        private string PATH_UPD;
        private long CHUNK_LENGTH;
        private SqlConnection _conn;

        public ExchangeFiles()
        {
            PATH_IN = ConfigurationManager.AppSettings["PATH_IN"];
            PATH_OUT = ConfigurationManager.AppSettings["PATH_OUT"];
            PATH_UPD = ConfigurationManager.AppSettings["PATH_UPD"];
            CHUNK_LENGTH = Convert.ToInt64(ConfigurationManager.AppSettings["CHUNK_LENGTH"]);
            _conn = new SqlConnection(ConfigurationManager.ConnectionStrings["ExchangeFilesDB"].ConnectionString);
            //if (!Directory.Exists(PATH_IN)) Directory.CreateDirectory(PATH_IN);
            //if (!Directory.Exists(PATH_OUT)) Directory.CreateDirectory(PATH_OUT);
            if (!Directory.Exists(PATH_OUT)) Directory.CreateDirectory(PATH_UPD);
            Authenticate(@"ftp", @"FARMAS", @"ahe7Qw$");
        }

        public void Dispose()
        {
            if (_conn.State == ConnectionState.Open) _conn.Close();
        }

        public FileInfoDC[] GetFilesNamesOut(int clientID)
        {
            InsLog(clientID, false, "Получение списка файлов для загрузки с сервера", "", "");
            FileInfoDC[] files = new FileInfoDC[0];       
            try
            {    
                PatsForExchangeFiles[] pats = GetPaths(clientID, "out");
                foreach (PatsForExchangeFiles p in pats)
                {
                    FileInfoDC[] fidc = GetFiles(p.Path);
                    if (fidc.Length > 0)
                    {
                        InsLog(clientID, false, "Есть файлы для загрузки", "Папка:" + p.Path, "");
                        Array.Resize(ref files, files.Length + fidc.Length);
                        for (int i = 0; i < fidc.Length; i++)
                        {
                            files[files.Length - fidc.Length + i] = new FileInfoDC() { Path = fidc[i].Path, CountChunk = fidc[i].CountChunk, Size = fidc[i].Size, MD5Checksum = fidc[i].MD5Checksum, MapFile = p.Map, ForAll = p.ForAll };
                            InsLog(clientID, false, "Файлов для загрузки с сервера", fidc[i].Path, "");
                        }
                        InsLog(clientID, false, "Cписок файлов для загрузки с сервера получен", "Папка:" + p.Path, "");
                    }
                    else InsLog(clientID, false, "Нет файлов для загрузки с сервера", "Папка:" + p.Path, "");
                }
                
            }
            catch (Exception ex)
            {
                InsLog(clientID, true, "Получение списка файлов для загрузки с сервера", "", ex.Message);
                throw new Exception("WcfServiceExchangeFiles.GetFilesNamesOut(int clientID)\n" + ex.Message);
            }
            return files;
        }

        public FileInfoDC[] GetFilesNamesIn(int clientID)
        {
            InsLog(clientID, false, "Поиск файлов в папке входящих файлов на сервере", "", "");
            FileInfoDC[] files = new FileInfoDC[0];
            try
            {
                PatsForExchangeFiles[] pats = GetPaths(clientID, "in");
                foreach (PatsForExchangeFiles p in pats)
                {
                    FileInfoDC[] fidc = GetFiles(p.Path);
                    if (fidc.Length > 0)
                    {
                        Array.Resize(ref files, files.Length + fidc.Length);
                        for (int i = 0; i < fidc.Length; i++)
                            files[files.Length - fidc.Length + i] = new FileInfoDC() { Path = fidc[i].Path, CountChunk = fidc[i].CountChunk, Size = fidc[i].Size, MD5Checksum = files[i].MD5Checksum, MapFile = p.Map, ForAll = p.ForAll };
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception("WcfServiceExchangeFiles.GetFilesNamesIn(int clientID)\n" + ex.Message);
            }
            return files;
        }

        private FileInfoDC[] GetFiles(string path)
        {
            var result = Directory.GetFiles(path);
            var fileInfoDC = new FileInfoDC[result.Length];
            try
            {

                for (var i = 0; i < result.Length; i++)
                {
                    fileInfoDC[i] = new FileInfoDC();
                    fileInfoDC[i].Path = result[i];//Path.GetFileName(result[i]);
                    fileInfoDC[i].CountChunk = (int)Math.Ceiling(new FileInfo(result[i]).Length / (double)CHUNK_LENGTH);
                    fileInfoDC[i].Size = new FileInfo(result[i]).Length;
                    fileInfoDC[i].MD5Checksum = ComputeMD5Checksum(result[i]);
                }
            }
            catch (Exception ex)
            {
                throw new Exception("WcfServiceExchangeFiles.GetFilesNamesOut()\n" + ex.Message);
            }
            return fileInfoDC;
        }

        private FileInfoDC GetFileInfo(string pathFile)
        {
            var fileInfoDC = new FileInfoDC();
            try
            {
                fileInfoDC.Path = pathFile;//Path.GetFileName(result[i]);
                fileInfoDC.CountChunk = (int)Math.Ceiling(new FileInfo(pathFile).Length / (double)CHUNK_LENGTH);
                fileInfoDC.Size = new FileInfo(pathFile).Length;
                fileInfoDC.MD5Checksum = ComputeMD5Checksum(pathFile);             
            }
            catch (Exception ex)
            {
                throw new Exception("WcfServiceExchangeFiles.GetFilesNamesOut()\n" + ex.Message);
            }
            return fileInfoDC;
        }

        public long GetChunkLeight()
        {
            return CHUNK_LENGTH;
        }

        public ChunkInfo GetChunk(string name, int chunkNumber)
        {
            //var stream = File.OpenRead(PATH_OUT + @"\" + name);
            //var fileLenght = new FileInfo(PATH_OUT + @"\" + name).Length;
            var stream = File.OpenRead(name);
            var fileLenght = new FileInfo(name).Length;
            var chunk = new byte[(int)Math.Min(CHUNK_LENGTH, fileLenght - chunkNumber * CHUNK_LENGTH)];
            ChunkInfo ret = new ChunkInfo();
            try
            {
                stream.Position = chunkNumber * CHUNK_LENGTH;
                stream.Read(chunk, 0, chunk.Length);
                ret.Chunk = Convert.ToBase64String(chunk);
                ret.MD5Checksum = ComputeMD5Checksum(chunk);
            }
            catch (Exception ex)
            {
                throw new Exception("WcfServiceExchangeFiles.GetChunk(string,int)\n" + ex.Message);
            }
            finally
            {
                stream.Close();
            }
            return ret;
        }

        public bool SendChunk(string name,bool map, int chunkNumber, string chunk, int countBytes, string md5, bool newFile, bool endFile)
        {
            var filename = name + "$";//PATH_IN + @"\" + name + "$";
            if (File.Exists(name)) File.Delete(name);
            if (!File.Exists(filename) || newFile) File.Create(filename).Close();
            var stream = File.Open(filename,FileMode.Append,FileAccess.Write);
            var chunkBytes = Convert.FromBase64String(chunk);
            bool ret = false;
            var fileLenght = new FileInfo(filename).Length;
            try
            {
                if (chunkBytes.Length == countBytes)
                    if (md5 == ComputeMD5Checksum(chunkBytes))
                    {
                        stream.Write(chunkBytes, 0, chunkBytes.Length);
                        stream.Flush();
                        stream.Close();
                        var fileLengtNew = new FileInfo(filename).Length;
                        if (fileLengtNew == fileLenght + chunkBytes.Length) ret = true;
                        else
                        {
                            if (fileLengtNew > fileLenght)
                            {
                                byte[] badFile = File.ReadAllBytes(filename);
                                stream = File.Open(filename, FileMode.CreateNew);
                                stream.Write(badFile, 0, (int)fileLenght);
                                stream.Flush();
                            }
                            ret = false;
                        }
                        if (endFile)
                        {
                            File.Move(filename, filename.TrimEnd('$'));
                        }
                    }
            }
            catch (Exception ex)
            {
                ret = false;
                throw new Exception("WcfServiceExchangeFiles.SendChunk(string,int,string,int,bool)\n" + ex.Message);
            }
            finally
            {
                stream.Close();
            }
            return ret;
        }

        public string GetPathIn()
        {
            return PATH_IN;
        }

        public string GetPathOut()
        {
            return PATH_OUT;
        }

        public bool DelFileFromServOut(string fileName)
        {
            try
            {
                File.Delete(fileName);
                return true;
            }
            catch (Exception)
            {
                //throw new Exception("WcfServiceExchangeFiles.DelFileFromServOut(string)\n" + ex.Message);
                return false;
            }
        }

        public bool DelFileFromClientOut(FileInfoDC fileInfo)
        {
            bool ret = false;
            try
            {
                //string file = new FileInfo(fileInfo.Path).Name;
                if (File.Exists(fileInfo.Path))
                    if (new FileInfo(fileInfo.Path).Length == fileInfo.Size)
                        if (fileInfo.MD5Checksum == ComputeMD5Checksum(fileInfo.Path)) 
                            ret = true;
            }
            catch (Exception ex)
            {
                throw new Exception("WcfServiceExchangeFiles.DelFileFromClientOut(FileInfoDC)\n" + ex.Message);
            }
            return ret;
        }

        static string ComputeMD5Checksum(string path)
        {
            string s = "";
            try
            {
                using (FileStream fs = File.OpenRead(path))
                {
                    byte[] fileData = new byte[fs.Length];
                    fs.Read(fileData, 0, (int)fs.Length);
                    byte[] checkSum = new MD5CryptoServiceProvider().ComputeHash(fileData);
                    s = BitConverter.ToString(checkSum).Replace("-", String.Empty);
                    fs.Close();
                }
            }
            catch (Exception ex)
            {
                throw new Exception("ComputeMD5Checksum(string path)\n" + ex.Message);
            }
            return s;
        }

        static string ComputeMD5Checksum(byte[] bytes)
        {
            var checkSum = new MD5CryptoServiceProvider().ComputeHash(bytes);
            return BitConverter.ToString(checkSum).Replace("-", String.Empty);
        }

        public int NewClient(string ip, DataTable files, int pharmCode)
        {
            SPparams[] param = new SPparams[3];
            param[0] = new SPparams() { Name = "@ip", Value = ip };
            param[1] = new SPparams() { Name = "@files", Value = files };
            param[2] = new SPparams() { Name = "@pharmcode", Value = pharmCode };
            int clientID = Convert.ToInt32(new SqlStorProc().SqlProcExecScalar(_conn, "wcf_NewClient", 10, param));
            InsLog(clientID, false, "Первый запуск клиента", "", "");
            return clientID;
        }

        public void ClientPharmCodeUpd(int clientid, int pharmCode)
        {
            InsLog(clientid, false, "Изменение кода аптеки", "", "");
            SPparams[] param = new SPparams[2];
            param[0] = new SPparams() { Name = "@clientid", Value = clientid };
            param[1] = new SPparams() { Name = "@pharmcode", Value = pharmCode };
            new SqlStorProc().SqlProcExecNonQuery(_conn, "wcf_ClientPharmCodeUpd", 10, param);
        }

        public void IAmFree(int clientid)
        {
            InsLog(clientid, false, "Начало нового сеанса связи", "", "");
            SPparams[] param = new SPparams[1];
            param[0] = new SPparams() { Name = "@clientid", Value = clientid };
            new SqlStorProc().SqlProcExecWithQuery(_conn, "wcf_IAmFree", 10, param);
        }

        public void ClientIPUpd(int clientid, string newip)
        {
            InsLog(clientid, false, "Изменение IP аптеки", "", "");
            SPparams[] param = new SPparams[2];
            param[0] = new SPparams() { Name = "@clientid", Value = clientid };
            param[1] = new SPparams() { Name = "@newip", Value = newip };
            new SqlStorProc().SqlProcExecNonQuery(_conn, "wcf_ClientIPUpd", 10, param);
        }

        public FileNameVersion GetInstallerInfo()
        {
            FileNameVersion fnv = new FileNameVersion();
            SPparams[] param = new SPparams[0];

            foreach (DataRow row in (new SqlStorProc().SqlProcExecWithQueryOneTable(_conn, "wcf_GetInstallerInfo", 10, param)).Rows)
            {
                fnv.Fileid = Convert.ToInt32(row[0]);
                fnv.FileName = PATH_UPD + @"\" + row[1].ToString();
                fnv.FileVersion = row[3].ToString();
            }

            return fnv;
        }

        public FileNameVersion GetWinServiceInfo()
        {
            FileNameVersion fnv = new FileNameVersion();
            SPparams[] param = new SPparams[0];

            foreach (DataRow row in (new SqlStorProc().SqlProcExecWithQueryOneTable(_conn, "wcf_GetWinServiceInfo", 10, param)).Rows)
            {
                fnv.Fileid = Convert.ToInt32(row[0]);
                fnv.FileName = PATH_UPD + @"\" + row[1].ToString();
                fnv.FileVersion = row[3].ToString();
            }

            return fnv;
        }

        public void ClientFileVersionUpd(int clientid, string falename, string fileversion)
        {
            InsLog(clientid, false, "Поиск новой версии программы файлообмена", "", "");
            SPparams[] param = new SPparams[3];
            param[0] = new SPparams() { Name = "@clientid", Value = clientid };
            param[1] = new SPparams() { Name = "@falename", Value = falename };
            param[2] = new SPparams() { Name = "@fileversion", Value = fileversion };

            new SqlStorProc().SqlProcExecNonQuery(_conn, "wcf_ClientFileVersionUpd", 10, param);
        }

        public FileInfoDC[] GetUpdFiles()
        { 
            return GetFiles(PATH_UPD);
        }

        private PatsForExchangeFiles[] GetPaths(int clientId, string inOut)
        {
            InsLog(clientId, false, "Получение путей для загрузки файлов с сервера", "", "");
            SPparams[] param = new SPparams[2];
            param[0] = new SPparams() { Name = "@clientid", Value = clientId };
            param[1] = new SPparams() { Name = "@inout", Value = inOut };
            DataTable dt = new SqlStorProc().SqlProcExecWithQueryOneTable(_conn, "PathsSel", 10, param);
            PatsForExchangeFiles[] pats = new PatsForExchangeFiles[dt.Rows.Count];
            for (int i = 0; i < dt.Rows.Count; i++)
            {
                pats[i] = new PatsForExchangeFiles() { Path = dt.Rows[i][0].ToString(), ForAll = Convert.ToBoolean(dt.Rows[i][1]), Map = Convert.ToBoolean(dt.Rows[i][2]) };
            }
            if (dt.Rows.Count == 0) InsLog(clientId, true, "Нет путей для загрузки файлов с сервера", "", "");
            return pats;
        }

        public string GetPathsFromServerIN(int clientId, bool map)
        {
            InsLog(clientId, false, "Получение пути для загрузки файлов на сервер", "", "");
            var pats = GetPaths(clientId, "in");
            foreach (PatsForExchangeFiles p in pats)
                if (p.Map == map)
                {
                    InsLog(clientId, false, "Путь для загрузки файлов на сервер определен", p.Path, "");
                    return p.Path;
                }
            InsLog(clientId, true, "Не определен путь для загрузки файлов на сервер", "", "");
            return "";
        }

        /*******************логин для службы****************/
        [DllImport("advapi32.dll")]
        protected static extern bool LogonUser(String lpszUsername,String lpszDomain,String lpszPassword,int dwLogonType,int dwLogonProvider,out int phToken);

        public static bool Authenticate(string userName, string domain, string password)
        {
            bool loggedOn = false;
            try
            {
                int token;
                loggedOn = LogonUser(userName, domain, password, 2, 0, out token);
                IntPtr token2 = new IntPtr(token);
                WindowsIdentity mWI = new WindowsIdentity(token2);
                WindowsImpersonationContext mwic = mWI.Impersonate();
            }
            catch (Exception ex)
            {
                throw new SecurityException("WcfServiceExchangeFiles.AuthenticateAuthenticate(string userName, string domain, string password)\n" + ex.Message);
            }
            return loggedOn;
        }
        /****************************************************/

        public FileInfoDC FileExists(string falePath)
        {
            FileInfoDC file = new FileInfoDC();
            try
            {
                if (File.Exists(falePath)) file = GetFileInfo(falePath);
            }
            catch (Exception ex)
            {
                throw new Exception("FileExists(string falePath)\n" + ex.Message);
            }
            return file;
        }

        private void InsLog(int clientId,bool error,string logMessage,string pathFile,string exception)
        {
            SPparams[] sp = new SPparams[2];
            sp[0] = new SPparams() { Name = "@ClientID", Value = clientId };
            sp[1] = new SPparams() { Name = "@error", Value = error };
	
            if (logMessage != "")
            {
                Array.Resize(ref sp, sp.Length + 1);
                sp[sp.Length - 1] = new SPparams() { Name = "@LogMessage", Value = logMessage };
            }
            if (pathFile != "")
            {
                Array.Resize(ref sp, sp.Length + 1);
                sp[sp.Length - 1] = new SPparams() { Name = "@PathFile", Value = pathFile };
            }
            if (exception != "")
            {
                Array.Resize(ref sp, sp.Length + 1);
                sp[sp.Length - 1] = new SPparams() { Name = "@ExceptionMessage", Value = exception };
            }
            new SqlStorProc().SqlProcExecNonQuery(_conn, "LogsIns", 30, sp);
        }
    }

    public class PatsForExchangeFiles
    {
        string path = "";
        bool forAll = false;
        bool map = false;

        public string Path
        {
            get { return path; }
            set { path = value; }
        }

        public bool ForAll
        {
            get { return forAll; }
            set { forAll = value; }
        }

        public bool Map
        {
            get { return map; }
            set { map = value; }
        }
    }
}
